<?php
/**
  * @uage:

        $dao = NICED_DaoFactory::create('test.test', 'select');
        $dao->setWhere('name="nana"');
        $dao->byId(3, true);
        $dao->setName('nana');
        $dao->setId(5);
        $rs = $dao->execute();
        $rows = $rs->fetchAll();
        var_dump($dao->getSQLWrapper()->render());


        $sql = "select * from test limit 1";
        $rs = $dao->execute($sql);


        var_dump($db->query($sql));
  * @desc dao class file
  * @author nana
  * @date 2011
  * @need sql class
  *
  */
class NICED_Dao 
{
	public    $sqlWrapper;//sql object
	public    $dsn;

	protected $db;
	protected $execHandler;
	protected $tableName;
    protected $rebuildTableName = false;//child can change this value, in order to dynamic build table name 
    protected $lookupDsn = false;
    protected static $maxLookupDsn = 100;
    protected static $tableDsnMap = array();
    private static $dsnTableName = "dsn_tablename";

	public function __construct() 
	{

	}

	protected function createDB($dsn = null) 
	{
        if (!$dsn)
        {
            $dsn = $this->getDsn();
        }
        if (!$dsn)
        {
            throw new NICED_Exception("no dsn passed in");
        }
		$this->db = NICED_DBFactory::create($dsn);

		return $this->db;
	}

	public function getDsn() 
	{
        if(!$this->lookupDsn) 
        {
		    return $this->dsn;
        }

        return $this->lookupDsn();
	}

    public function lookupDsn()
    {
        $default_dsn = $this->dsn;
        $table = str_replace('`', '', strtolower($this->getTableNameForDsn()));

        if(!$table) 
        {
            return $default_dsn;
        }

        if(isset(self::$tableDsnMap[$table])) 
        {
            return self::$tableDsnMap[$table];
        }

        while(count(self::$tableDsnMap) > self::$maxLookupDsn) 
        {
            array_shift(self::$tableDsnMap);
        }

        $cache = MemcacheFactory::create("query_memcache"); 
        $dsn = $cache->get($table);
        if($dsn) 
        {
            return self::$tableDsnMap[$table] = $dsn;
        }

        $db = NICED_DBFactory::create('system');
        $rs = $db->query(sprintf("SELECT `dsn` FROM `".self::$dsnTableName."` WHERE `tablename` = '%s'", $table));
        if(!$rs->isSuccess()) 
        {
            return self::$tableDsnMap[$table] = '';
        }

        $row = $rs->fetchRow();
        $dsn = isset($row["dsn"]) ? $row["dsn"] : NULL;

        if(!$dsn) 
        {
            if(!($dsn = $this->buildUnmappedTableDsn())) 
            {
                return '';
            }

            $rs = $db->query(sprintf("INSERT INTO `".self::$dsnTableName."` (`tablename`, `dsn`) VALUES ('%s', '%s')", $table, $dsn));
            if(! $rs->isSuccess()) 
            {
                return self::$tableDsnMap[$table] = '';
            }
        }

        $cache->set($table, $dsn, 0, 60);

        return $dsn;
    }

    public function getDB()
    {
        return $this->db;
    }

    public function getTableNameForDsn()
    {
        return $this->rebuildTableName();
    }

    public function getTableName()
    {
        return $this->tableName;
    }

    public function buildUnmappedTableDsn()
    {
        return $this->dsn;
    }

    public function execute($sql = null) 
    {
        if (!$sql)
        {
            $sql = $this->getSQLWrapper()->render();
        }

        return $this->getExecHandler()->performQuery($this, $this->dsn, $sql);
    }

    public function performQuery($dao, $dsn, $sql) 
    {
        $db  = $dao->createDB();
        $rst = $db->query($sql);

		return $this->setRs($rst);
	}


	public function setRS($rs) 
	{ 
		if(method_exists($this, 'formatrow')) 
		{
			$rs->addRowFormatter(array($this, 'formatRow'));
		}

		return $rs;
	}

	public function getExecHandler() 
	{
		return ($this->execHandler) ? $this->execHandler : $this;
	}

	public function setExecHandler($dao) 
	{
		return $this->execHandler = $dao;
	}

    public function attach($dao)
    {
        $dao->setExecHandler($this);

        return $dao;
    }

	//set sql object
	public function setSQLWrapper($sql)
	{
		$this->sqlWrapper = $sql;
	}

    public function getSQLWrapper()
    {
        if ($this->rebuildTableName)
        {
            $table_name = $this->rebuildTableName();
		    $this->sqlWrapper->setFrom($table_name);
        }

        return $this->sqlWrapper;
    }

	
} //end class

class NICED_RealTimeDao extends NICED_Dao 
{
	protected $tablePrefix;
	protected $fields = array();//for define table field
    protected $pairs  = array();
	private static   $callPrefixes = array('get', 'set');

    public function __construct($name, $config = null, $sql_type = 'select')
    {
		$this->resolveConfig($name, $config);

	    $sql = new Niced_SQLWrapper(str_replace('sql', '', strtolower($sql_type)), $this->getTableName());
        $this->setSQLWrapper($sql);

        parent::__construct();
    }

	public function getFields()
	{
		return $this->fields;
	}

	public function resolveConfig($name, $config) 
	{
		if (!isset($config['dao']))
        {
            throw new NICED_Exception("no dao passed in");
        }

        $dao_config = $config['dao'];

        if (!$dao_config)
        {
            throw new NICED_Exception("no dao in config");
        }

        $this->dsn = isset($dao_config['dsn']) ? $dao_config['dsn'] : null;

        if (!$this->dsn) 
		{
            throw new NICED_NotExistsException('DAO '.$name.' not found dsn');
        }

        $this->tableName = isset($dao_config['table']) ? $dao_config['table'] : null;

        if (!$this->tableName) 
		{
            throw new NICED_NotExistsException('DAO '.$name.' has no table name ');
        }
	}

	//for setField, $dao->setName(), $dao->getName() eg.
	public function __call($method, $args)
	{
		$method = strtolower($method);
        list($cmd, $needle) = $this->resolveCall($method);

        return $this->$cmd($needle, $args);		
	}

    protected function resolveCall($method)
    {
        $prefixes = &self::$callPrefixes;

        foreach($prefixes as $prefix)
        {
            if(substr($method, 0, strlen($prefix)) == $prefix) return array('retrieve'.ucwords($prefix), substr($method, strlen($prefix)));
        }

        throw new NICED_Exception('invalid method call to '.$method);
    }

	public function set($field, $v, $type = null)
	{
		$this->getSQLWrapper()->set($field, $v, $type);
	}

    protected function retrieveSet($needle, $args)
	{
        $v = isset($args[0]) ?  $args[0] : NULL;

        if ($this->fields && !in_array($needle, $this->fields))
        {
            return $this->pairs[$needle] = $v;
        }
        else
        {
            return $this->set($needle, $v);
        }
    }

	protected function get($n)
	{ 
		return $this->getSQLWrapper()->get($n);
	}

    protected function retrieveGet($n)
    {
        if ($this->fields && !in_array($n, $this->fields))
        {
            $value = $this->pairs[$n];
        }
        else
        {
		    $value = $this->get($n);
        }

		return $value;
    }

	//for set sql param, can use $dao->getSQLWrapper()->setWhere();
	public function byId($v, $use_append = false) 
	{ 
		return $this->byField('id', $v, $use_append); 
	}

	public function byField($field, $v, $use_append = false)
    {
        $method = ($use_append) ? 'appendWhere' : 'setWhere';

        if (isset($this->tablePrefix) && $this->tablePrefix) 
		{
            $field = $this->tablePrefix.'.'.$field;
        }

		$ids = $this->filterIds($v);

		if(count($ids) < 1) 
		{
			throw new NICED_Exception('no valid ids in list');
		}

		if(count($ids) == 1) 
		{
			return $this->setSQLParam($method, sprintf("%s = %s", $field, $ids[0]));
		}

		return $this->setSQLParam($method, sprintf('%s IN (%s)', $field, implode(', ', $ids))); 
    }

    public function setWhere($v)
    {
        return $this->setSQLParam("setWhere", $v);
    }

    public function appendWhere($v)
    {
        return $this->setSQLParam("appendWhere", $v);
    }

    public function setSQLType($type)
    {
        $this->getSQLWrapper()->setType($type);
    }

    //for CRUD
    public function update()
    {
        $this->setSQLType("update");
    }

    public function delete()
    {
        $this->setSQLType("delete");

        return $this->execute();
    }

    public function create()
    {
        $this->setSQLType("insert");

        return $this->execute();
    }

    public function load()
    {
        $this->setSQLType("select");

        return $this->execute();
    }

    public function replace()
    {
        $this->setSQLType("replace");

        return $this->execute();
    }

    public function filterIds($ids)
    {
        if(is_scalar($ids)) 
		{
			$ids = explode(',', $ids);
		}

        if(!is_array($ids) || sizeof($ids) < 1) 
		{
			return array();
		}

        $filtered_ids = array();

        foreach($ids as $id)
        {
            if(!is_scalar($id)) 
			{
				continue;
			}

            if($id != 0) 
			{
				$id = ltrim($id, '0 ');
			}

            if(preg_match("/^[-+]?[0-9]+$/", $id) && !in_array($id, $filtered_ids)) 
			{
				$filtered_ids[] = $id;
			}
        }

        sort($filtered_ids);

        return $filtered_ids;
    }

	//$this->setSQLParam('setLimit', $v);
	//$this->setSQLParam('appendGroup', $v); 
	public function setSQLParam($method, $v)
	{ 
		return $this->getSQLWrapper()->$method($v); 
	}

	public static function resolveCondition($data) 
	{
		$condition = null;
		$conditions = array();
		foreach ($data as $field => $value) 
		{
			switch ($field) 
			{
				case 'time' : 
				{
					foreach ($value as $k => $v) 
					{
						$field = $k;
						$start_time = isset($v['start']) ? $v['start'] : null;
						$stop_time  = isset($v['stop']) ? $v['stop'] : null;
						
						if ($start_time) 
						{				
							if ($stop_time) 
							{
								$conditions[] = $field." BETWEEN '".$start_time."' AND '".$stop_time ."'";
							} 
							else 
							{
								$conditions[] = $field." >= '".$start_time."'";
							}
						}
					}
					break;
				}
				default: 
				{
					switch (gettype($value)) 
					{
						case 'array' : 
						{
							$conditions[] = $field." IN ("."".implode(',', $value).")";
							break;
						}
						default : 
						{
							$conditions[] = $field."="."'".$value."'";
						}
					}
				}
			}
			
		}
		
		$condition = implode(' AND ', $conditions);

		return $condition;
	}

} //end class


/**
  * @usage $txn = new NICED_TransactionDao();
  *        $dao = NICED_DaoFactory::create('test');
  *        $txn->attach($dao);
  *        $dao->execute();
  *        $txn->commit();
  *
  */
class NICED_TransactionDao extends NICED_Dao 
{
    protected $connectors = array();
    private   $separateDBLink = true;

	public function __construct()
	{
	}

    public function performQuery($dao, $dsn, $sql) 
	{
        $db  = $this->createDB($dsn);
		$rst = $db->query($sql);

		$rst  = $this->setRS($rst);
		$rows = $dao->setRS($rst); 
		
        return  $rows;
    }

    public function setSeparateDBLink($bool)
    {
        $this->separateDBLink = $bool;
    }
     
    public function createDB($dsn = null) 
	{
		if (!$dsn) 
		{
			throw new NICED_ValidationException("no dsn passed！");
		}

        if ($this->separateDBLink)
        {
           $dsn = $dsn.'.transaction'.mt_rand(0, 10000000); 
        }

        if (isset($this->connectors[$dsn])) 
		{
			return $this->connectors[$dsn];
		}

		$db = NICED_DBFactory::create($dsn);

        $txn = $db->createTransaction();
        $this->connectors[$dsn] = $txn;
		//begin txn
        $txn->begin();

        return $this->connectors[$dsn];
    } 
    
    public function commit() 
	{
        if (!$this->isExistsConnectors()) 
		{
        	return false;
        }
        $status = false;

        foreach(array_keys($this->connectors) as $k)
        {
            $status = $this->connectors[$k]->commit();
            if (!$status)
            {
                return $this->rollback(); 
            }
        }

        $this->resetConnectors();

        return $status;
    }

    public function rollback() 
	{
        if(!($this->connectors)) 
		{
			return true;
		}

        foreach(array_keys($this->connectors) as $k) 
		{
            $this->connectors[$k]->rollback();
        }

        $this->resetConnectors();

        return true;
    }

    public  function getExecHandler() 
	{
        return $this;
    }
    
    public function setRS($rs) 
	{
        if(!$rs->isSuccess()) 
		{
			$this->rollback();
		}

		$rs = parent::setRS($rs);
		
        return $rs;
    }

	public function closeAllConnectors()
	{
		foreach(array_keys($this->connectors) as $k) 
		{
			if (method_exists($this->connectors[$k], 'close')) 
			{
				$this->connectors[$k]->close();
			}
		}	

		return true;
	}

    public function resetConnectors()
    {
		$this->closeAllConnectors();
        $this->connectors = array();
    }

    public function isExistsConnectors() 
	{
    	return (empty($this->connectors)) ? false : true;
    }
    
    public  function __destruct()
	{
        if($this->isExistsConnectors()) 
		{
			$this->rollback();
		}
    }

} //end class
